﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
#if WINDOWS
using System.Threading.Tasks;
#endif
using Microsoft.Xna.Framework.Content;

namespace DARE
{
    public sealed class CAnimation : IAnimation
    {
        #region Fields

        private TimeSpan m_totalTime;
        private string m_name;
        private List<CKeyFrame> m_allFrames;

        [ContentSerializer]
        private double m_timeByFrame;

        [ContentSerializer]
        private Dictionary<string, CAnimConfig> m_anims;

        #endregion

        #region Properties

        [ContentSerializer]
        public CHierarchy Hierarchy { get; private set; }

        [ContentSerializer]
        public List<CKeyFrame> AllFrames
        {
            get { return this.m_allFrames; }
            private set
            {
                if (this.m_allFrames != value)
                {
                    this.m_allFrames = value;
                    ReLoadData();
                }
            }
        }

        [ContentSerializer]
        public TimeSpan Duration
        {
            get { return this.m_totalTime; }
            private set { this.m_totalTime = value; }
        }

        [ContentSerializer]
        public bool Finialized { get; private set; }

        [ContentSerializer]
        public int Length { get; internal set; }

        [ContentSerializer]
        public string Name
        {
            get { return m_name; }
            private set
            {
                if (this.m_name != value)
                {
                    this.m_name = value;
                    //CAnimationMgr.AddAnimation(this);
                }
            }
        }

        #endregion

        #region CTOR

        public CAnimation()
        {
        }

        public CAnimation(string _name, CAnimNode[] _bones) //, bool _skeletonAnimation
        {
            Name = _name;
            m_allFrames = new List<CKeyFrame>();
            Hierarchy = new CHierarchy(_bones);
            this.m_anims = new Dictionary<string, CAnimConfig>();
        }

        #endregion

        #region Method

        public void Add(CKeyFrame frame)
        {
            Debug.Assert(Finialized == false);

            m_allFrames.Add(frame);

            if (frame.Time > m_totalTime)
                m_totalTime = frame.Time;
        }

        public void Add(IEnumerable<CKeyFrame> frames)
        {
            Debug.Assert(Finialized == false);

            foreach (var frame in frames)
                Add(frame);
        }

        public void Finalize()
        {
            Finalize(null);
        }

        public void Finalize(IEnumerable<CAnimNode> _bonesAnimated)
        {
            Finialized = true;
            m_timeByFrame = (this.m_totalTime.TotalMilliseconds / m_allFrames.Count);
            ReLoadData();
            FinilizaFrameTime();
            if (_bonesAnimated != null)
                FinalizeNodes(_bonesAnimated);
        }

        private void FinilizaFrameTime()
        {
            float startId = 0;
            float endId = -1;
            var _times = (from el in m_allFrames orderby el.Time ascending select el.Time).ToList();
            var _startTime = _times[0].TotalMilliseconds;
            var _endTime = _times[_times.Count - 1].TotalMilliseconds;
            this.m_anims.Add(this.Name, new CAnimConfig(this, this.Name, _startTime, _endTime));
        }

        private List<CTransform> GetFrame(CAnimConfig animConfig, bool revert, out int previousKeyFrame, out int nextKeyFrame)
        {
            int index = (revert) ? 1 : -1;
            for (int i = animConfig.m_lastIndex; i != animConfig.m_target; i += animConfig.m_incre)
            {
                CKeyFrame _frame = m_allFrames[i];
                if (((revert) ? (_frame.Time.TotalMilliseconds < animConfig.Elapsed) : (_frame.Time.TotalMilliseconds > animConfig.Elapsed)))
                {
                    int _prevIndex = i + index;
                    CKeyFrame _prevFrame = m_allFrames[_prevIndex];
                    if (revert)
                    {
                        CKeyFrame _tmp = _frame;
                        _frame = _prevFrame;
                        _prevFrame = _tmp;
                    }
                    float _d = (float)(_frame.Time.TotalMilliseconds - _prevFrame.Time.TotalMilliseconds);
                    _d = (float)(_frame.Time.TotalMilliseconds - animConfig.Elapsed) / _d;
                    previousKeyFrame = _prevIndex;
                    nextKeyFrame = i;
                    return _prevFrame.Lerp(_frame, 1.0f - _d);
                }
                else if (_frame.Time.TotalMilliseconds == animConfig.Elapsed)
                {
                    previousKeyFrame = i + index;
                    nextKeyFrame = i - index;
                    return _frame.Pose;
                }
            }
            Debug.Assert(false, "Can never access here");

            previousKeyFrame = -1;
            nextKeyFrame = -1;
            return null;
        }

        public int GetNodeIndexByName(string name)
        {
            return Hierarchy[name];
        }

        public List<CTransform> GetFrame(CAnimConfig animConfig, out int previousKeyFrame, out int nextKeyFrame)
        {
            return GetFrame(animConfig, (animConfig.State & EAnimState.REVERSE) == EAnimState.REVERSE, out previousKeyFrame, out nextKeyFrame);
        }

        //public void CutAnimation(int idStart, int idEnd, string name)
        //{
        //    Debug.Assert(idStart >= 0 && idStart < idEnd, "Bad Id frame");
        //    Debug.Assert(!m_anims.ContainsKey(name), "Name Already existing");

        //    m_anims.Add(name, new CAnimConfig(this, name, idStart, idEnd));
        //}

        public void CutAnimation(int duration)
        {
            
            List<CKeyFrame> frames = new List<CKeyFrame>();
            for (int i = 0; i < m_allFrames.Count && i < duration; ++i)
            {
                frames.Add(m_allFrames[i]);
            }
            m_allFrames = frames;
            Length = m_allFrames.Count;
            Duration = m_allFrames.Last().Time;
        }

        public void CutAnimation(int startID, int endID, string name)
        {
            if (startID < 0 || startID > endID || endID >= m_allFrames.Count)
                return;
            List<CKeyFrame> frames = new List<CKeyFrame>();
            for (int i = startID; i < endID; ++i)
                frames.Add(m_allFrames[i]);
            m_allFrames = frames;
            Length = m_allFrames.Count;
            Duration = frames.Last().Time;
        }

        public void CutAnimation(double startTime, double endTime, string name)
        {
            //Debug.Assert(idStart >= 0 && idStart < idEnd, "Bad Id frame");
            //Debug.Assert(!m_anims.ContainsKey(name), "Name Already existing");
            if (!m_anims.ContainsKey(name))
                m_anims.Add(name, new CAnimConfig(this, name, startTime, endTime));
        }

        private void FinalizeNodes(IEnumerable<CAnimNode> _nodesAnimated)
        {
            List<CAnimNode> _bone = new List<CAnimNode>();
            int indexRemoved = 0;
            for (int i = 0; i < Hierarchy.NodesCount; ++i)
            {
                if (!_nodesAnimated.Contains(Hierarchy[i]))
                {
#if WINDOWS
                    Parallel.ForEach(m_allFrames, (e) => { e.RemoveTransform(i - indexRemoved); });
#else
                    foreach (CKeyFrame keyFrame in m_allFrames)
                        keyFrame.RemoveTransform(i - indexRemoved);
#endif
                    ++indexRemoved;
                }
                else
                    _bone.Add(Hierarchy[i]);
            }
            Hierarchy = new CHierarchy(_bone.ToArray());
        }

        //// USELESS
        private void ReLoadData()
        {
            Length = m_allFrames.Count;
        }

        public double GetTimeByFrameId(int id)
        {
            if (id < 0 || this.Duration.TotalMilliseconds < this.m_timeByFrame * id)
                return this.Duration.TotalMilliseconds;
            return this.m_timeByFrame * id;
        }

        public List<CAnimConfig> GetAnims()
        {
            return (from el in m_anims select el.Value).ToList();

        #endregion
    }
}
}
